/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2021. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.harmony.recyclerview;

import com.harmony.recyclerview.collection.LongSparseArray;
import com.harmony.recyclerview.logger.HiLogs;
import com.harmony.recyclerview.support.Observable;

import ohos.agp.colors.RgbColor;
import ohos.agp.components.*;

import ohos.agp.components.element.ShapeElement;
import ohos.agp.utils.Color;
import ohos.agp.utils.MimeData;
import ohos.app.Context;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.ref.WeakReference;
import java.util.*;

/**
 * RecyclerComponent having the layout implementation
 */
public class RecyclerComponent extends DirectionalLayout {
    /**
     * Default position value
     */
    public static final int NO_POSITION = -1;

    private static String TAG = RecyclerComponent.class.getName();
    private static final int HORIZONTAL = Component.HORIZONTAL;
    private static final int VERTICAL = Component.VERTICAL;
    private static final int DEFAULT_ORIENTATION = VERTICAL;
    private static final long NO_ID = -1;
    private static final int INVALID_TYPE = -1;
    private static final long FOREVER_NS = Long.MAX_VALUE;
    private static final int GRID_LAYOUT = 0;
    private static final int LINEAR_LAYOUT = 1;
    private final TableLayoutManager mGridManager = new TableLayoutManager();
    private final RecyclerComponentDataObserver mObserver = new RecyclerComponentDataObserver();
    private List<ViewHolder> holders = new ArrayList<>();
    private LongSparseArray<ViewHolder> holderList = new LongSparseArray<>();
    private final ScrollView mScroller;
    private final TableLayout mGridLayout;
    private final DirectionalLayout mHeaderView;
    private Adapter mAdapter;


    public RecyclerComponent(Context context) {
        super(context);
    }

    public RecyclerComponent(Context context, AttrSet attrSet) {
        super(context, attrSet);
    }

    public RecyclerComponent(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
    }

    {
        this.setOrientation(VERTICAL);

        mScroller = new ScrollView(mContext);
        HiLogs.e(TAG, "initLayoutView : " + mContext);
        ComponentContainer.LayoutConfig scrollerParam = new ComponentContainer.LayoutConfig();
        scrollerParam.width = ComponentContainer.LayoutConfig.MATCH_PARENT;
        scrollerParam.height = ComponentContainer.LayoutConfig.MATCH_PARENT;
        mScroller.setLayoutConfig(scrollerParam);
        this.setOrientation(VERTICAL);

        mHeaderView = new DirectionalLayout(mContext);
        ComponentContainer.LayoutConfig headerParam = new ComponentContainer.LayoutConfig();
        headerParam.width = ComponentContainer.LayoutConfig.MATCH_PARENT;
        headerParam.height = ComponentContainer.LayoutConfig.MATCH_CONTENT;
        mHeaderView.setLayoutConfig(headerParam);
        this.addComponent(mHeaderView);
        mHeaderView.setVisibility(HIDE);
        this.addComponent(mScroller);

        mGridLayout = new TableLayout(mContext);
        ComponentContainer.LayoutConfig gridParam = new ComponentContainer.LayoutConfig();
        gridParam.width = ComponentContainer.LayoutConfig.MATCH_PARENT;
        gridParam.height = ComponentContainer.LayoutConfig.MATCH_CONTENT;
        mGridLayout.setLayoutConfig(gridParam);
        mGridLayout.setEnabled(true);
        mGridLayout.setOrientation(DEFAULT_ORIENTATION);
        mGridLayout.setLayoutManager(mGridManager);
        mScroller.addComponent(mGridLayout);

        mScroller.setScrolledListener(new ScrolledListener() {
            @Override
            public void onContentScrolled(Component component, int index, int index1, int index2, int index3) {
            }
        });

        mGridLayout.setComponentStateChangedListener(new ComponentStateChangedListener() {
            @Override
            public void onComponentStateChanged(Component component, int index) {
            }
        });

        mGridLayout.setBindStateChangedListener(new BindStateChangedListener() {
            @Override
            public void onComponentBoundToWindow(Component component) {
            }

            @Override
            public void onComponentUnboundFromWindow(Component component) {
            }
        });
    }

    /**
     * setTouchEventListner
     *
     * @param listener touchEventListener
     */
    public void setTouchEventListner(TouchEventListener listener) {
        mGridLayout.setTouchEventListener(listener);
    }

    /**
     * getTouchEventListner
     *
     * @return listener
     */
    public TouchEventListener getTouchEventListner() {
        return mGridLayout.getTouchEventListener();
    }

    /**
     * onDrag
     *
     * @param component Component param
     * @param event     Drag events param
     * @return dragEvents
     */
    public boolean onDrag(Component component, DragEvent event) {
        return mGridLayout.onDrag(component, event);
    }

    /**
     * startDragAndDropComponent
     *
     * @param data           mime data param
     * @param shadowProvider drag feedback provider param
     * @return drag and drop layout events
     */
    public boolean startDragAndDropComponent(MimeData data, Component.DragFeedbackProvider shadowProvider) {
        return mGridLayout.startDragAndDrop(data, shadowProvider);
    }

    /**
     * setDraggedListener
     *
     * @param dragMode int dragmode
     * @param listener component drag listener
     */
    public void setDraggedListener(int dragMode, Component.DraggedListener listener) {
        mGridLayout.setDraggedListener(dragMode, listener);
    }

    /**
     * setListOrientation
     *
     * @param direction int direction value
     */
    public void setListOrientation(int direction) {
        if (mGridManager != null) {
            mGridManager.setOrientation(direction);
        }
    }

    /**
     * setSpanCount
     *
     * @param spanCount span count value
     */
    public void setSpanCount(int spanCount) {
        if (mGridLayout != null) {
            mGridLayout.setColumnCount(spanCount);
        }
    }

    /**
     * setSpanCount
     *
     * @param component as a header layout
     */
    public void addHeader(Component component){
        if (component != null){
            mHeaderView.addComponent(component);
        }
    }

    /**
     * setSpanCount
     *
     * @param container as a header layout
     */
    public void addHeader(ComponentContainer container){
        if (container != null){
            mHeaderView.addComponent(container);
        }
    }

    /**
     * setAdapter
     *
     * @param adapter setting the adapter
     */
    public void setAdapter(@Nullable Adapter adapter) {
        this.mAdapter = adapter;
        mAdapter.registerAdapterDataObserver(mObserver);
        requestLayout();
    }

    /**
     * getComponentPosition
     *
     * @param component Component param
     * @return int position value
     */
    public int getComponentPosition(Component component) {
        if (component instanceof TableLayout) {
            HiLogs.e("MainAbilitySlice", "Component : Table");
        }
        return 0; // Integer.parseInt((String) component.getTag());
    }

    private void requestLayout() {
        HiLogs.d(TAG, "Empty List Size  : " + holders.size());
        if (mAdapter != null) {
            int count = mAdapter.getItemCount();

            for (int index = 0; index < count; index++) {
                final ViewHolder holder = setupViewHolder(index);
                if (mGridLayout != null) {
                    mGridLayout.addComponent(holder.itemView, index);
                    mAdapter.bindViewHolder(holder, index);
                }
            }
        }
    }

    ViewHolder setupViewHolder(int index) {
        int viewType;
        if (mGridManager.getColumnCount() == 1) {
            viewType = LINEAR_LAYOUT;
        } else {
            viewType = GRID_LAYOUT;
        }
        ViewHolder holder = mAdapter.createViewHolder(mGridLayout, viewType, index);
        ComponentContainer.LayoutConfig lp = holder.itemView.getLayoutConfig();
        int size = getWidth(mGridLayout);
        lp.width = size;
//        holder.itemView.setWidth(size);
        if (lp == null) {
            lp = generateDefaultLayoutParams(size);
            holder.itemView.setLayoutConfig(lp);
        }
        holder.mPosition = index;
        holder.mItemId = holder.itemView.getId();
        holders.add(index, holder);
        return holder;
    }

    private ComponentContainer.LayoutConfig generateDefaultLayoutParams(int size) {
        ComponentContainer.LayoutConfig lp = new ComponentContainer.LayoutConfig();
        lp.width = size;
        lp.height = ComponentContainer.LayoutConfig.MATCH_PARENT;
        return lp;
    }

    int getWidth(TableLayout mGridLayout){
        int screenWidth = AttrHelper.fp2px(mGridLayout.getContext().getResourceManager()
                .getDeviceCapability().width, mGridLayout.getContext());
        return screenWidth / mGridLayout.getColumnCount();
    }

    int getAdapterPositionFor(ViewHolder viewHolder) {
        if (viewHolder.hasAnyOfTheFlags(RecyclerComponent.ViewHolder.FLAG_INVALID
                | RecyclerComponent.ViewHolder.FLAG_REMOVED
                | RecyclerComponent.ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)
                || !viewHolder.isBound()) {
            return RecyclerComponent.NO_POSITION;
        }
        return viewHolder.mPosition; // mAdapterHelper.applyPendingUpdatesToPosition(viewHolder.mPosition);
    }

    /**
     * Adapter class having ViewHolder
     */
    public abstract static class Adapter<VH extends ViewHolder> {
        private final AdapterDataObservable mObservable = new AdapterDataObservable();
        private boolean mHasStableIds = false;

        /**
         * Abstract methods for the create holders
         *
         * @param parent   component container
         * @param viewType int view type
         * @param index    int value
         * @return VH viewholder
         */
        public abstract VH onCreateViewHolder(ComponentContainer parent, int viewType, int index);

        /**
         * Abstract methods for the bind holders
         *
         * @param holder   VH
         * @param position int value
         */
        public abstract void onBindViewHolder(VH holder, int position);

        /**
         * onBindViewHolder
         *
         * @param holder   view golder param
         * @param position int value param
         * @param payloads list value param
         */
        public void onBindViewHolder(VH holder, int position, List<Object> payloads) {
            onBindViewHolder(holder, position);
        }

        /**
         * createViewHolder
         *
         * @param parent   Component container parent value
         * @param viewType view value
         * @param index    int value
         * @return ViewHolder
         */
        public final VH createViewHolder(ComponentContainer parent, int viewType, int index) {
            try {
                final VH holder = onCreateViewHolder(parent, viewType, index);
                if (holder.itemView.getComponentParent() != null) {
                    throw new IllegalStateException("ViewHolder views must not be attached when"
                            + " created. Ensure that you are not passing 'true' to the attachToRoot"
                            + " parameter of LayoutInflater.inflate(..., boolean attachToRoot)");
                }
                holder.mItemViewType = viewType;
                return holder;
            } finally {
                HiLogs.i(TAG, "createViewHolder() end");
            }
        }

        /**
         * bindViewHolder
         *
         * @param holder   viewholder value
         * @param position view value
         */
        public final void bindViewHolder(VH holder, int position) {
            holder.mPosition = position;
            if (hasStableIds()) {
                holder.mItemId = getItemId(position);
            }
            holder.setFlags(ViewHolder.FLAG_BOUND,
                    ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
                            | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
            onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
            holder.clearPayload();
        }

        /**
         * getItemViewType
         *
         * @param position view value
         * @return int value
         */
        public int getItemViewType(int position) {
            return 0;
        }

        /**
         * setHasStableIds
         *
         * @param hasStableIds boolean value
         */
        public void setHasStableIds(boolean hasStableIds) {
            if (hasObservers()) {
                throw new IllegalStateException("Cannot change whether this adapter has "
                        + "stable IDs while the adapter has registered observers.");
            }
            mHasStableIds = hasStableIds;
        }

        /**
         * getItemId
         *
         * @param position int value
         * @return long value
         */
        public long getItemId(int position) {
            return NO_ID;
        }

        /**
         * getItemCount
         *
         * @return int value
         */
        public abstract int getItemCount();

        /**
         * getItem
         *
         * @param position int value
         * @return Object value
         */
        public abstract Object getItem(int position);

        /**
         * hasStableIds
         *
         * @return boolean value
         */
        public final boolean hasStableIds() {
            return mHasStableIds;
        }

        /**
         * onViewRecycled
         *
         * @param holder viewholder
         */
        public void onViewRecycled(VH holder) {
        }

        /**
         * onFailedToRecycleView
         *
         * @param holder viewholder
         * @return boolean value
         */
        public boolean onFailedToRecycleView(VH holder) {
            return false;
        }

        /**
         * onViewAttachedToWindow
         *
         * @param holder viewholder
         */
        public void onViewAttachedToWindow(VH holder) {
        }

        /**
         * onViewDetachedFromWindow
         *
         * @param holder viewholder
         */
        public void onViewDetachedFromWindow(VH holder) {
        }

        /**
         * hasObservers
         *
         * @return boolean observers
         */
        public final boolean hasObservers() {
            return mObservable.hasObservers();
        }

        /**
         * registerAdapterDataObserver
         *
         * @param observer adapter observers
         */
        public void registerAdapterDataObserver(AdapterDataObserver observer) {
            mObservable.registerObserver(observer);
        }

        /**
         * unregisterAdapterDataObserver
         *
         * @param observer adapter observers
         */
        public void unregisterAdapterDataObserver(AdapterDataObserver observer) {
            mObservable.unregisterObserver(observer);
        }

        /**
         * onAttachedToRecyclerComponent
         *
         * @param recyclerView recycler component
         */
        public void onAttachedToRecyclerComponent(RecyclerComponent recyclerView) {
        }

        /**
         * onDetachedFromRecyclerComponent
         *
         * @param recyclerView recycler component
         */
        public void onDetachedFromRecyclerComponent(RecyclerComponent recyclerView) {
        }

        /**
         * notifyDataSetChanged
         */
        public final void notifyDataSetChanged() {
            mObservable.notifyChanged();
        }

        /**
         * notifyItemChanged
         *
         * @param position int value
         */
        public final void notifyItemChanged(int position) {
            mObservable.notifyItemRangeChanged(position, 1);
        }

        /**
         * getItemId
         *
         * @param position int value
         * @param payload  object value
         */
        public final void notifyItemChanged(int position, @Nullable Object payload) {
            mObservable.notifyItemRangeChanged(position, 1, payload);
        }

        /**
         * notifyItemRangeChanged
         *
         * @param positionStart int value
         * @param itemCount     int count value
         */
        public final void notifyItemRangeChanged(int positionStart, int itemCount) {
            mObservable.notifyItemRangeChanged(positionStart, itemCount);
        }

        /**
         * notifyItemRangeChanged
         *
         * @param positionStart int value
         * @param itemCount     int value
         * @param payload       Object value
         */
        public final void notifyItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
            mObservable.notifyItemRangeChanged(positionStart, itemCount, payload);
        }

        /**
         * notifyItemInserted
         *
         * @param position int value
         */
        public final void notifyItemInserted(int position) {
            mObservable.notifyItemRangeInserted(position, 1);
        }

        /**
         * notifyItemMoved
         *
         * @param fromPosition int value
         * @param toPosition   int value
         */
        public final void notifyItemMoved(int fromPosition, int toPosition) {
            mObservable.notifyItemMoved(fromPosition, toPosition);
        }

        /**
         * notifyItemRangeInserted
         *
         * @param positionStart int value
         * @param itemCount     int value
         */
        public final void notifyItemRangeInserted(int positionStart, int itemCount) {
            mObservable.notifyItemRangeInserted(positionStart, itemCount);
        }

        /**
         * notifyItemRemoved
         *
         * @param position int value
         */
        public final void notifyItemRemoved(int position) {
            mObservable.notifyItemRangeRemoved(position, 1);
        }

        /**
         * notifyItemRangeRemoved
         *
         * @param positionStart int value
         * @param itemCount     int value
         */
        public final void notifyItemRangeRemoved(int positionStart, int itemCount) {
            mObservable.notifyItemRangeRemoved(positionStart, itemCount);
        }

        public void setHeader(Component component){
//            addHeader(component);
        }
    }

    /**
     * AdapterDataObserver class
     */
    public abstract static class AdapterDataObserver {
        /**
         * onChanged
         */
        public void onChanged() {
            // Do nothing
        }

        /**
         * onItemRangeChanged
         *
         * @param positionStart int value
         * @param itemCount     int value
         */
        public void onItemRangeChanged(int positionStart, int itemCount) {
            HiLogs.e(TAG, "AdapterDataObserver : onItemRangeChanged2(int positionStart, int itemCount)");
            // do nothing
        }

        /**
         * onItemRangeChanged
         *
         * @param positionStart int value
         * @param itemCount     int value
         * @param payload       Object value
         */
        public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
            /** fallback to onItemRangeChanged(positionStart, itemCount) if app does not override this method. */
            HiLogs.e(TAG, "AdapterDataObserver : onItemRangeChanged1(int positionStart, int itemCount)");
            onItemRangeChanged(positionStart, itemCount);
        }

        /**
         * onItemRangeInserted
         *
         * @param positionStart int value
         * @param positionStart int value
         */
        public void onItemRangeInserted(int positionStart, int itemCount) {
            // do nothing
        }

        /**
         * onItemRangeRemoved
         *
         * @param positionStart int value
         * @param positionStart int value
         */
        public void onItemRangeRemoved(int positionStart, int itemCount) {
            // do nothing
        }

        /**
         * onItemRangeMoved
         *
         * @param fromPosition int value
         * @param toPosition   int value
         * @param itemCount    int value
         */
        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
            // do nothing
        }
    }

    private class RecyclerComponentDataObserver extends AdapterDataObserver {
        RecyclerComponentDataObserver() {
        }

        @Override
        public void onChanged() {
            HiLogs.d(TAG, "onChanged()");
        }

        @Override
        public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
            for (int index = positionStart; index < positionStart + itemCount; index++) {
                ViewHolder viewHolder = holders.get(index);
                mAdapter.onBindViewHolder(viewHolder, index);
                HiLogs.e("onItemRangeChanged", "Index : " + index);
            }
        }

        @Override
        public void onItemRangeInserted(int positionStart, int itemCount) {
            if (mGridLayout != null && mAdapter != null) {
                for (int index = positionStart; index < positionStart + itemCount; index++) {
                    final ViewHolder holder = setupViewHolder(index);
                    if (mGridLayout != null) {
                        mGridLayout.addComponent(holder.itemView, index);
                        mAdapter.bindViewHolder(holder, index);
                        HiLogs.e("onItemRangeInserted", "Index : " + index);
                    }
                }
                updateViewHolderIndex(positionStart + itemCount);
            }
        }

        @Override
        public void onItemRangeRemoved(int positionStart, int itemCount) {
            if (mGridLayout != null) {
                mGridLayout.removeComponents(positionStart, itemCount);
                for (int index = positionStart; index < positionStart + itemCount; index++) {
                    holders.remove(index);
                    // Pending update remainig holders indexex
                }
            }
        }

        @Override
        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
        }
    }

    private void updateViewHolderIndex(int newIndexes) {
        for (int index = newIndexes; index < holders.size(); index++) {
            HiLogs.e("onItemRangeInserted", "entered in loop");
            ViewHolder holder = holders.remove(index);
            holder.mOldPosition = holder.mPosition;
            holder.mPosition = index;
            holders.add(index, holder);
            HiLogs.e("onItemRangeInserted", "Index : " + index);
        }
    }

    /**
     * AdapterDataObservable class extending AdapterDataObserver
     */
    static class AdapterDataObservable extends Observable<AdapterDataObserver> {
        /**
         * hasObservers
         *
         * @return observers boolean value
         */
        public boolean hasObservers() {
            return !mObservers.isEmpty();
        }

        /**
         * notifyChanged
         */
        public void notifyChanged() {
            // since onChanged() is implemented by the app, it could do anything, including
            // removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }

        /**
         * notifyItemRangeChanged
         *
         * @param positionStart int value
         * @param itemCount     int value
         */
        public void notifyItemRangeChanged(int positionStart, int itemCount) {
            HiLogs.e(TAG, "AdapterDataObservable : onItemRangeChanged(int positionStart, int itemCount)");
            HiLogs.e(TAG, "AdapterDataObservable : mObservers Null : " + (mObservers == null));
            notifyItemRangeChanged(positionStart, itemCount, null);
        }

        /**
         * notifyItemRangeChanged
         *
         * @param positionStart int value
         * @param itemCount     int value
         * @param payload       Object value
         */
        public void notifyItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
            // since onItemRangeChanged() is implemented by the app, it could do anything, including
            // removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                HiLogs.e(TAG, "AdapterDataObservable : mObservers Size : " + (mObservers.size()));
                mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
            }
        }

        /**
         * notifyItemRangeInserted
         *
         * @param positionStart int value
         * @param itemCount     int value
         */
        public void notifyItemRangeInserted(int positionStart, int itemCount) {
            // since onItemRangeInserted() is implemented by the app, it could do anything,
            // including removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
            }
        }

        /**
         * notifyItemRangeRemoved
         *
         * @param positionStart int value
         * @param itemCount     int value
         */
        public void notifyItemRangeRemoved(int positionStart, int itemCount) {
            // since onItemRangeRemoved() is implemented by the app, it could do anything, including
            // removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
            }
        }

        /**
         * notifyItemMoved
         *
         * @param fromPosition int value
         * @param toPosition   int value
         */
        public void notifyItemMoved(int fromPosition, int toPosition) {
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onItemRangeMoved(fromPosition, toPosition, 1);
            }
        }
    }

    /**
     * Recycler class
     */
    public final class Recycler {
        /**
         * unscrapView
         *
         * @param viewHolder View holder value
         */
        public void unscrapView(ViewHolder viewHolder) {
        }
    }

    /**
     * ViewHolder class
     */
    public abstract static class ViewHolder {
        /**
         * This ViewHolder has been bound to a position; mPosition, mItemId and mItemViewType
         * are all valid.
         */
        static final int FLAG_BOUND = 1 << 0;

        /**
         * The data this ViewHolder's view reflects is stale and needs to be rebound
         * by the adapter. mPosition and mItemId are consistent.
         */
        static final int FLAG_UPDATE = 1 << 1;

        /**
         * This ViewHolder's data is invalid. The identity implied by mPosition and mItemId
         * are not to be trusted and may no longer match the item view type.
         * This ViewHolder must be fully rebound to different data.
         */
        static final int FLAG_INVALID = 1 << 2;

        /**
         * This ViewHolder points at data that represents an item previously removed from the
         * data set. Its view may still be used for things like outgoing animations.
         */
        static final int FLAG_REMOVED = 1 << 3;

        /**
         * This ViewHolder should not be recycled. This flag is set via setIsRecyclable()
         * and is intended to keep views around during animations.
         */
        static final int FLAG_NOT_RECYCLABLE = 1 << 4;

        /**
         * This ViewHolder is returned from scrap which means we are expecting an addView call
         * for this itemView. When returned from scrap, ViewHolder stays in the scrap list until
         * the end of the layout pass and then recycled by RecyclerView if it is not added back to
         * the RecyclerView.
         */
        static final int FLAG_RETURNED_FROM_SCRAP = 1 << 5;

        /**
         * This ViewHolder is fully managed by the LayoutManager. We do not scrap, recycle or remove
         * it unless LayoutManager is replaced.
         * It is still fully visible to the LayoutManager.
         */
        static final int FLAG_IGNORE = 1 << 7;

        /**
         * When the View is detached form the parent, we set this flag so that we can take correct
         * action when we need to remove it or add it back.
         */
        static final int FLAG_TMP_DETACHED = 1 << 8;

        /**
         * Set when we can no longer determine the adapter position of this ViewHolder until it is
         * rebound to a new position. It is different than FLAG_INVALID because FLAG_INVALID is
         * set even when the type does not match. Also, FLAG_ADAPTER_POSITION_UNKNOWN is set as soon
         * as adapter notification arrives vs FLAG_INVALID is set lazily before layout is
         * re-calculated.
         */
        static final int FLAG_ADAPTER_POSITION_UNKNOWN = 1 << 9;

        /**
         * Set when a addChangePayload(null) is called
         */
        static final int FLAG_ADAPTER_FULLUPDATE = 1 << 10;

        /**
         * Used by ItemAnimator when a ViewHolder's position changes
         */
        static final int FLAG_MOVED = 1 << 11;

        /**
         * Used by ItemAnimator when a ViewHolder appears in pre-layout
         */
        static final int FLAG_APPEARED_IN_PRE_LAYOUT = 1 << 12;

        /**
         * Used when a ViewHolder starts the layout pass as a hidden ViewHolder but is re-used from
         * hidden list (as if it was scrap) without being recycled in between.
         * <p>
         * When a ViewHolder is hidden, there are 2 paths it can be re-used:
         * a) Animation ends, view is recycled and used from the recycle pool.
         * b) LayoutManager asks for the View for that position while the ViewHolder is hidden.
         * <p>
         * This flag is used to represent "case b" where the ViewHolder is reused without being
         * recycled (thus "bounced" from the hidden list). This state requires special handling
         * because the ViewHolder must be added to pre layout maps for animations as if it was
         * already there.
         */
        static final int FLAG_BOUNCED_FROM_HIDDEN_LIST = 1 << 13;

        private static final List<Object> FULLUPDATE_PAYLOADS = Collections.emptyList();

        /**
         * Component variable for the item
         */
        @NotNull
        public final Component itemView;
        WeakReference<RecyclerComponent> mNestedRecyclerView;
        int mPosition = NO_POSITION;
        int mOldPosition = NO_POSITION;
        long mItemId = NO_ID;
        int mItemViewType = INVALID_TYPE;
        int mPreLayoutPosition = NO_POSITION;

        // The item that this holder is shadowing during an item change event/animation
        ViewHolder mShadowedHolder = null;

        // The item that is shadowing this holder during an item change event/animation
        ViewHolder mShadowingHolder = null;
        int mFlags;
        List<Object> mPayloads = null;
        List<Object> mUnmodifiedPayloads = null;

        // If non-null, view is currently considered scrap and may be reused for other data by the
        // scrap container.
        Recycler mScrapContainer = null;

        // Keeps whether this ViewHolder lives in Change scrap or Attached scrap
        boolean mInChangeScrap = false;
        RecyclerComponent mOwnerRecyclerView;


        private int mIsRecyclableCount = 0;

        public ViewHolder(@NotNull Component itemView) {
            if (itemView == null) {
                throw new IllegalArgumentException("itemView may not be null");
            }
            this.itemView = itemView;
        }

        void flagRemovedAndOffsetPosition(int mNewPosition, int offset, boolean applyToPreLayout) {
            addFlags(ViewHolder.FLAG_REMOVED);
            offsetPosition(offset, applyToPreLayout);
            mPosition = mNewPosition;
        }

        void offsetPosition(int offset, boolean applyToPreLayout) {
            if (mOldPosition == NO_POSITION) {
                mOldPosition = mPosition;
            }
            if (mPreLayoutPosition == NO_POSITION) {
                mPreLayoutPosition = mPosition;
            }
            if (applyToPreLayout) {
                mPreLayoutPosition += offset;
            }
            mPosition += offset;
        }

        void clearOldPosition() {
            mOldPosition = NO_POSITION;
            mPreLayoutPosition = NO_POSITION;
        }

        void saveOldPosition() {
            if (mOldPosition == NO_POSITION) {
                mOldPosition = mPosition;
            }
        }

        boolean shouldIgnore() {
            return (mFlags & FLAG_IGNORE) != 0;
        }

        /**
         * getPosition
         *
         * @return position int value
         */
        @Deprecated
        public final int getPosition() {
            return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition;
        }

        /**
         * getLayoutPosition
         *
         * @return layout position int value
         */
        public final int getLayoutPosition() {
            return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition;
        }

        /**
         * getAdapterPosition
         *
         * @return adapter position int value
         */
        public final int getAdapterPosition() {
            if (mOwnerRecyclerView == null) {
                return NO_POSITION;
            }
            return mOwnerRecyclerView.getAdapterPositionFor(this);
        }

        /**
         * getOldPosition
         *
         * @return position int value
         */
        public final int getOldPosition() {
            return mOldPosition;
        }

        /**
         * getItemId
         *
         * @return long item value
         */
        public final long getItemId() {
            return mItemId;
        }

        /**
         * getItemViewType
         *
         * @return int item value type
         */
        public final int getItemViewType() {
            return mItemViewType;
        }

        boolean isScrap() {
            return mScrapContainer != null;
        }

        void unScrap() {
            mScrapContainer.unscrapView(this);
        }

        boolean wasReturnedFromScrap() {
            return (mFlags & FLAG_RETURNED_FROM_SCRAP) != 0;
        }

        void clearReturnedFromScrapFlag() {
            mFlags = mFlags & ~FLAG_RETURNED_FROM_SCRAP;
        }

        void clearTmpDetachFlag() {
            mFlags = mFlags & ~FLAG_TMP_DETACHED;
        }

        void stopIgnoring() {
            mFlags = mFlags & ~FLAG_IGNORE;
        }

        void setScrapContainer(Recycler recycler, boolean isChangeScrap) {
            mScrapContainer = recycler;
            mInChangeScrap = isChangeScrap;
        }

        boolean isInvalid() {
            return (mFlags & FLAG_INVALID) != 0;
        }

        boolean needsUpdate() {
            return (mFlags & FLAG_UPDATE) != 0;
        }

        boolean isBound() {
            return (mFlags & FLAG_BOUND) != 0;
        }

        boolean isRemoved() {
            return (mFlags & FLAG_REMOVED) != 0;
        }

        boolean hasAnyOfTheFlags(int flags) {
            return (mFlags & flags) != 0;
        }

        boolean isTmpDetached() {
            return (mFlags & FLAG_TMP_DETACHED) != 0;
        }

        boolean isAttachedToTransitionOverlay() {
            return itemView.getComponentParent() != null && itemView.getComponentParent() != mOwnerRecyclerView;
        }

        boolean isAdapterPositionUnknown() {
            return (mFlags & FLAG_ADAPTER_POSITION_UNKNOWN) != 0 || isInvalid();
        }

        void setFlags(int flags, int mask) {
            mFlags = (mFlags & ~mask) | (flags & mask);
        }

        void addFlags(int flags) {
            mFlags |= flags;
        }

        void addChangePayload(Object payload) {
            if (payload == null) {
                addFlags(FLAG_ADAPTER_FULLUPDATE);
            } else if ((mFlags & FLAG_ADAPTER_FULLUPDATE) == 0) {
                createPayloadsIfNeeded();
                mPayloads.add(payload);
            } else {
                HiLogs.d(TAG, "payload val:" + payload);
            }
        }

        private void createPayloadsIfNeeded() {
            if (mPayloads == null) {
                mPayloads = new ArrayList<Object>();
                mUnmodifiedPayloads = Collections.unmodifiableList(mPayloads);
            }
        }

        void clearPayload() {
            if (mPayloads != null) {
                mPayloads.clear();
            }
            mFlags = mFlags & ~FLAG_ADAPTER_FULLUPDATE;
        }

        List<Object> getUnmodifiedPayloads() {
            if ((mFlags & FLAG_ADAPTER_FULLUPDATE) == 0) {
                if (mPayloads == null || mPayloads.size() == 0) {
                    // Initial state,  no update being called.
                    return FULLUPDATE_PAYLOADS;
                }
                // there are none-null payloads
                return mUnmodifiedPayloads;
            } else {
                // a full update has been called.
                return FULLUPDATE_PAYLOADS;
            }
        }

        void resetInternal() {
            mFlags = 0;
            mPosition = NO_POSITION;
            mOldPosition = NO_POSITION;
            mItemId = NO_ID;
            mPreLayoutPosition = NO_POSITION;
            mIsRecyclableCount = 0;
            mShadowedHolder = null;
            mShadowingHolder = null;
            clearPayload();
        }

        @Override
        public String toString() {
            String className =
                    getClass().isAnonymousClass() ? "ViewHolder" : getClass().getSimpleName();
            final StringBuilder sb = new StringBuilder(className + "{"
                    + Integer.toHexString(hashCode()) + " position=" + mPosition + " id=" + mItemId
                    + ", oldPos=" + mOldPosition + ", pLpos:" + mPreLayoutPosition);

            if (isScrap()) {
                sb.append(" scrap ")
                        .append(mInChangeScrap ? "[changeScrap]" : "[attachedScrap]");
            }

            if (isInvalid()) {
                sb.append(" invalid");
            }

            if (!isBound()) {
                sb.append(" unbound");
            }

            if (needsUpdate()) {
                sb.append(" update");
            }

            if (isRemoved()) {
                sb.append(" removed");
            }

            if (shouldIgnore()) {
                sb.append(" ignored");
            }

            if (isTmpDetached()) {
                sb.append(" tmpDetached");
            }

            if (!isRecyclable()) {
                sb.append(" not recyclable(" + mIsRecyclableCount + ")");
            }

            if (isAdapterPositionUnknown()) {
                sb.append(" undefined adapter position");
            }

            if (itemView.getComponentParent() == null) {
                sb.append(" no parent");
            }

            sb.append("}");
            return sb.toString();
        }

        /**
         * Set to the recycle using the boolean value
         *
         * @param recyclable boolean value
         */
        public final void setIsRecyclable(boolean recyclable) {
            mIsRecyclableCount = recyclable ? mIsRecyclableCount - 1 : mIsRecyclableCount + 1;
            if (mIsRecyclableCount < 0) {
                mIsRecyclableCount = 0;
                HiLogs.e(TAG, "isRecyclable decremented below 0: "
                        + "unmatched pair of setIsRecyable() calls for " + this);
            } else if (!recyclable && mIsRecyclableCount == 1) {
                mFlags |= FLAG_NOT_RECYCLABLE;
            } else if (recyclable && mIsRecyclableCount == 0) {
                mFlags &= ~FLAG_NOT_RECYCLABLE;
            } else {
                HiLogs.d(TAG, "setIsRecyclable val:" + recyclable + ":" + this);
            }
        }

        /**
         * Returns the boolean state recycled
         *
         * @return true if this item is available to be recycled, false otherwise.
         */
        public final boolean isRecyclable() {
            return (mFlags & FLAG_NOT_RECYCLABLE) == 0;
        }

        /**
         * Returns whether we have animations referring to this view holder or not.
         * This is similar to isRecyclable flag but does not check transient state.
         *
         * @return shouldBeKeptAsChild value
         */
        boolean shouldBeKeptAsChild() {
            return (mFlags & FLAG_NOT_RECYCLABLE) != 0;
        }

        /**
         * Returns the doesTransientStatePreventRecycling state
         *
         * @return True if ViewHolder is not referenced by RecyclerView animations but has
         * transient state which will prevent it from being recycled.
         */
        boolean doesTransientStatePreventRecycling() {
            return (mFlags & FLAG_NOT_RECYCLABLE) == 0;
        }

        boolean isUpdated() {
            return (mFlags & FLAG_UPDATE) != 0;
        }
    }

    /**
     * ItemDecoration class
     */
    public class ItemDecoration {
    }
}